#!/usr/bin/env python


"""

entwinelib.py
=============

Desc: Library for querying twine.com

Author: Rob Cakebread <cakebread a t gmail .com>

License  : New BSD (See COPYING)

"""

__docformat__ = 'restructuredtext'

import os
import urllib2
import logging
from urlparse import urlparse

import workerpool
from rdflib import Namespace, ConjunctiveGraph
from rdfalchemy.orm import mapper
from rdfalchemy import rdfSubject, rdfSingle, rdfMultiple


from entwine.utils import (get_user_passwd, get_toplevel_path,
        get_base_path_url)


PROFILE_URL = 'http://www.twine.com/user'
ITEM_URL = 'http://www.twine.com/item'
TWINE_URL = 'http://www.twine.com/twine'
ATOM_URL = 'http://www.twine.com:80/feed/atom/entries/twines'

#Radar Network Ontologies
APP = Namespace('http://www.radarnetworks.com/shazam#')
BASIC = Namespace('http://www.radarnetworks.com/2007/09/12/basic#')
RADAR = Namespace('http://www.radarnetworks.com/core#')
WEB = Namespace('http://www.radarnetworks.com/web#')

#Other Ontologies used by Radar Networks
XMLNS = Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')


class User(rdfSubject):

    """
    Twine User Item  Class
    """

    rdf_type = APP.User

    status = rdfSingle(APP.status)
    was_invited_by = rdfSingle(APP.wasInvitedBy)
    location = rdfSingle(APP.location)
    url = rdfMultiple(BASIC.url)
    views = rdfSingle(WEB.views)
    is_person = rdfSingle(RADAR.isPerson)
    created_date = rdfSingle(RADAR.createdDate)
    last_modified_date = rdfSingle(RADAR.lastModifiedDate)
    was_created_by = rdfSingle(RADAR.wasCreatedBy)


class TwineConnection():

    """
    Defines a connection to the Twine server
    """

    def __init__(self):
        #Used for authentication to keep track of where we can go
        self.opener = {}
        self.logger = logging.getLogger("entwine")

    def connect(self, url, accept='application/rdf+xml'):
        """
        Gets connection to Twine server using authentication and sends header
        info using 'Accept application/rdf+xml' so we can fetch a Twine Item
        
        @param url: URL to fetch
        @type url: string
        @param accept: HTTP headder Accept
        @type accept: string

        @rtype: string
        @returns: string RDF/XML or accept type
        
        """
        toplevel = get_toplevel_path(url)
        password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
        username, password = get_user_passwd()
        password_mgr.add_password(None, toplevel, username, password)
        handler = urllib2.HTTPBasicAuthHandler(password_mgr)
        self.opener[toplevel] = urllib2.build_opener(handler)
        if accept:
            self.opener[toplevel].addheaders = [('Accept', accept), 
                ('Content-Type',  accept), 
                ('User-agent', 'entwine: http://code.google.com/p/entwine)')]
        self.logger.debug("connect: %s" % url)
        self.logger.debug("Accept: %s" % accept)

    def get(self, url, accept=None):
        """
        Fetch a Twine Item, using authentication

        @param: url
        @type: string

        @returns: string RDF/XML
        
        """
        toplevel = get_toplevel_path(url)
        if self.opener.has_key(toplevel):
            #No need to re-authenticate if we've been in this level of site
            return self.opener[toplevel].open(url)
        else:
            #Authenticate, then fetch
            self.logger.debug("AUTH: Authenticating %s" % toplevel)
            self.connect(url, accept)
            return self.opener[toplevel].open(url)


class TwineItem():

    """
    Class containing methods for manipulating Twine Items

    """

    def __init__(self, connection=None):
        #Item Ids for each person this person hasRelationshipTo
        self.relationships = []
        #Item Id for the person we're talking about
        self.is_person = None
        #Item RDF/XML string for the Item
        self.item_xml = None
        self.logger = logging.getLogger("entwine")
        if not connection:
            self.connection = TwineConnection()
        else:
            self.connection = connection

    def fetch_by_url(self, item_url, accept=None):
        """
        Fetch Twine Item
        
        @param: item_url
        @type: string
        
        @returns: RDF/XML
        @rtype: string

        """
        return self.connection.get(item_url, accept)

    def get_atom(self):
        """
        Return ATOM for user.

        @rtype: string
        @return: RDF/XML ATOM feed for authenticated user

        """
        return self.fetch_by_url(ATOM_URL, accept='application/atom+xml')


    def get_triple_object(self, item_xml, namespace, predicate):
        """
        Return object of a triple given item_xml

        @param: item_xml
        @type: string

        @param: namespace
        @type: rdflib Namespace

        @param: predicate
        @type: string

        @return: string of a triple object

        Example: get_triple_object(item_xml, RADAR, 'isPerson')

        """
        store = ConjunctiveGraph()
        store.parse(item_xml, publicID=None, format="xml")
        for subj, obj in store.subject_objects(namespace[predicate]):
            self.logger.debug(subj)
            self.logger.debug(predicate + u': %s' % obj)
            #We should yield this... so we can replace get_relationships
            #Actually we should use rdfalchemy!
            return obj

    def show(self, item_id):
        """
        Print Item to stdout

        @param item_id: Twine Item id string
        @type: string
        @returns: RDF XML string

        """
        print self.fetch_by_url("%s/%s" % (ITEM_URL, item_id)).read()


    def get_item(self, item_id):
        """
        Fetch an Item from a Twine

        @param item_id: Twine Item id string
        @type: string
        @rtype: file descriptor
        @returns: file descriptor for RDF/XML string

        """
        return self.fetch_by_url("%s/%s" % (ITEM_URL, item_id))

    def get_twine(self, name):
        """
        Fetch a Twine by its name

        @param name: Twine name
        @type: string
        @returns: file descriptor for RDF/XML string

        """
        return self.get_twine_by_name("%s/%s" % (TWINE_URL, name))

    def get_twine_by_name(self, twine_url):
        """
        Fetch Twine 
        
        @param: name
        @type: string
        
        @returns: RDF/XML
        @rtype: string

        """
        return self.connection.get(twine_url)

class WorkerJob(workerpool.Job):

    def __init__(self, item_id, verbose, con):
        self.item_id = item_id
        self.verbose = verbose
        self.con = con

    def run(self):
        username = self.con.get_username_from_item_id(self.item_id)
        item_xml = self.con.get_item(self.item_id)
        first_name = self.con.get_triple_object(item_xml, BASIC, 'firstName')
        item_xml = self.con.get_item(self.item_id)
        last_name = self.con.get_triple_object(item_xml, BASIC, 'lastName')
        if self.verbose:
            print self.item_id,
        print "%s %s - %s" % (first_name, last_name, username)

class TwineProfile(TwineItem):

    """
    Methods for fetching and manipulating a Twine User Profile
    which is a specific type of Twine Item

    """

    def __init__(self, item=None):
        TwineItem.__init__(self)
        self.item = item

    def get_profile(self, username):
        """
        Get RDF for a given username
        
        @param: username
        @type: string
        @returns: string of RDFL XML
        
        """
        return self.fetch_by_url("%s/%s" % (PROFILE_URL, username))
    

    def parse_profile(self, profile):
        """
        Parse RDF profile into triples and print them out.
        @param profile: RDF XML of a person's profile
        """
        
        store = ConjunctiveGraph()
        store.parse(profile, publicID=None, format="xml")
        self.print_profile_section(store, APP,
                ['status', 'wasInvitedBy', 'location'])
        self.print_profile_section(store, BASIC, ['url'])
        self.print_profile_section(store, RADAR, \
                ['isPerson', 'createdDate', 'lastModifiedDate'])
        self.print_profile_section(store, WEB, ['views'])
        self.logger.debug("Fetching RDF for isPerson Item id %s..." % \
                self.is_person)
        #Fetches User Item and prints its info
        #self.print_person_item(self.get_item(self.is_person))


    def get_relationships(self, username):
        """
        Return list of relationship Items for Username
        @param: username
        @type: string
        @return: list of Item Ids

        """
        self.is_person = self.get_item_id_by_username(username)
        if self.is_person is None:
            self.logger.error("Error: Couldn't get IsPerson")
            return None

        person_item = self.get_item(self.is_person)
        store = ConjunctiveGraph()
        store.parse(person_item, publicID=None, format="xml")
        predicate = 'hasRelationshipTo'
        for subj, obj in store.subject_objects(BASIC[predicate]):
            self.logger.debug(subj)
            self.logger.debug(predicate + u': %s' % obj)
            self.relationships.append(os.path.basename(urlparse(obj).path))
        return self.relationships

    def show_relationships(self, username, verbose):
        """
        Show relashonships for a user
        
        @param: username
        @type: string

        """
        self.get_relationships(username)
        for item_id in self.relationships:
            if verbose:
                print "%s/%s" % (ITEM_URL, item_id)
            else:
                print item_id

    def get_item_id_by_username(self, username):
        """
        Get the Item Id for a username

        @param: username
        @type: string
        @return: string of Twine Item Id
        """
        profile = self.get_profile(username)
        store = ConjunctiveGraph()
        store.parse(profile, publicID=None, format="xml")
        #First we need to find isPerson
        predicate = 'isPerson'
        for subj, obj in store.subject_objects(RADAR[predicate]):
            self.logger.debug(subj)
            self.logger.debug(predicate + u': %s' % obj)
            #Get the Item Id of this person so we can fetch that item
            #obj is entire URL to item, we just want the item id itself:
            return os.path.basename(urlparse(obj).path)

    def get_username_from_item_id(self, item_id):
        """
        Get username from an Item's Id
        
        @param: item_id
        @type: string
        @return: string of username
        
        """
        item_xml = self.get_item(item_id)
        #This gets you http://www.twine.com/user/<username>:
        is_user = self.get_triple_object(item_xml, RADAR, 'isUser')
        #We return just the username part:
        return get_base_path_url(is_user)

    def print_person_item(self, person):
        """
        Print a Person's Item
        
        @param person: string of RDF XML
        @returns: None
        
        """
        store = ConjunctiveGraph()
        store.parse(person, publicID=None, format="xml")
        self.print_profile_section(store, BASIC, ['lastName', 'firstName',
            'description', 'hasRelationshipTo'])

    def print_profile_section(self, store, namespace, elements):
        """Print the triples"""
        for predicate in elements:
            for subj, obj in store.subject_objects(namespace[predicate]):
                self.logger.debug(subj)
                print predicate + ': %s' % obj
                if predicate == 'isPerson':
                    #Get the Item Id of this person so we can fetch that item
                    #obj is entire URL to item, we just want the item id itself:
                    self.is_person = os.path.basename(urlparse(obj).path)
                elif predicate == 'hasRelationshipTo':
                    self.relationships.append(get_base_path_url(obj))

    def show_verbose_relationships(self, username, verbose):
        """
        Show relashonships for a user
        
        @param: username
        @type: string

        """
        pool = workerpool.WorkerPool(size=7)
        self.get_relationships(username)
        i = 0
        for item_id in self.relationships:
            job = WorkerJob(item_id, verbose, self)
            pool.put(job)
        pool.shutdown()
        pool.join()
        #What the...
        from time import sleep
        sleep(.001)

def _join_subjects(my_list):
    """
    Returns a comma-separated string from a list of rdfSubjects
    
    @param: my_list
    @type: list if rdfSubjects

    @returns: string 
    """
    new_list = []
    for subj in my_list:
        new_list.append(subj.resUri)
    return ", ".join(new_list)

def print_profile(username):

    rdfSubject.db = ConjunctiveGraph()
    profile = TwineProfile()
    xml = profile.get_profile("cakebread")
    rdfSubject.db.parse(xml)
    p = User.ClassInstances().next()
    print "isPerson: %s" % p.is_person.resUri
    print "createdDate: %s" % p.created_date
    print "lastModifiedDate: %s" % p.last_modified_date
    print "Status: %s" % p.status
    print "WasInvitedBy: %s" % p.was_invited_by.resUri
    print "Location: %s" % p.location
    print "Urls: %s" % _join_subjects(p.url)
    print "Views: %s" % p.views

def twine_test():
    """Quick and dirty test."""
    item = TwineItem()
    print item.get_atom().read()
    print_profile('cakebread')
    #print item.get_item("1tx52n3d-gv").read()
    #profile = TwineProfile()
    #profile.get_profile("cakebread").read()
    #profile.parse_profile(profile.get_profile("cakebread"))
    #profile.get_relationships("cakebread")


if __name__ == "__main__":
    twine_test()
